﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Threading.Tasks;
using AnthStat.WPF.Applications.Collections;

namespace AnthStat.WPF.Applications
{
    public abstract class ValidatableObservableObject : ObservableObject, INotifyDataErrorInfo
    {
        #region Members
        private readonly RecordErrorDictionary _errors = new RecordErrorDictionary();
        private readonly object _syncLock = new object();
        #endregion // Members

        #region Events
        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
        #endregion // Events

        #region Properties

        public virtual bool HasErrorsOnSelfOrOwnedObjects
        {
            get
            {
                return HasErrors;
            }
        }

        /// <summary>
        /// Stores errors for each field
        /// </summary>
        public virtual RecordErrorDictionary Errors { get { return this._errors; } }

        /// <summary>
        /// Gets whether the record currently has any errors
        /// </summary>
        public virtual bool HasErrors
        {
            get
            {
                return Errors.Any(kv => kv.Value != null && kv.Value.Count > 0);
            }
        }
        #endregion // Properties

        #region Methods

        protected override void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            base.OnPropertyChanged(e);

            //ValidateAsync();
            if (!(e.PropertyName.Equals("HasErrors", StringComparison.OrdinalIgnoreCase) ||
                e.PropertyName.Equals("HasErrorsOnSelfOrOwnedObjects", StringComparison.OrdinalIgnoreCase))
                && !String.IsNullOrEmpty(e.PropertyName))
            {
                Validate();
                RaisePropertyChanged("HasErrors");
                RaisePropertyChanged("HasErrorsOnSelfOrOwnedObjects");
            }
        }

        public virtual Task ValidateAsync()
        {
            return Task.Run(() => Validate());
        }

        public virtual void Validate()
        {
            lock (_syncLock)
            {
                var validationContext = new ValidationContext(this, null, null);
                var validationResults = new List<ValidationResult>();
                Validator.TryValidateObject(this, validationContext, validationResults, true);

                foreach (var kv in _errors.ToList())
                {
                    if (validationResults.All(r => r.MemberNames.All(m => m != kv.Key)))
                    {
                        List<string> outLi;
                        _errors.TryRemove(kv.Key, out outLi);
                        OnErrorsChanged(kv.Key);
                    }
                }

                var q = from r in validationResults
                        from m in r.MemberNames
                        group r by m into g
                        select g;

                foreach (var prop in q)
                {
                    var messages = prop.Select(r => r.ErrorMessage).ToList();

                    if (_errors.ContainsKey(prop.Key))
                    {
                        List<string> outLi;
                        _errors.TryRemove(prop.Key, out outLi);
                    }
                    _errors.TryAdd(prop.Key, messages);
                    OnErrorsChanged(prop.Key);
                }
            }
        }

        public virtual void OnErrorsChanged(string propertyName)
        {
            Contract.Requires(!String.IsNullOrEmpty(propertyName));

            var handler = ErrorsChanged;
            if (handler != null)
                handler(this, new DataErrorsChangedEventArgs(propertyName));
        }

        /// <summary>
        /// Gets the errors for a given property
        /// </summary>
        /// <param name="propertyName">The property to check</param>
        /// <returns>list of errors</returns>
        public virtual IEnumerable GetErrors(string propertyName)
        {
            List<string> errorsForName = new List<string>();
            Errors.TryGetValue(propertyName, out errorsForName);
            return errorsForName;
        }
        #endregion // Methods
    }
}
